package com.yuwei.config;

import com.yuwei.security.UserDetailsServiceImpl;
import com.yuwei.security.filter.JWTAuthenticationFilter;
import com.yuwei.security.filter.JWTAuthorizationFilter;
import com.yuwei.security.handler.*;
import com.yuwei.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;
import java.util.Map;

/**
 * security是基础，OAuth和Jwt是具体实现，在security上生效
     * OAuth2.0是规范
     * jwt是token的实现
     * 如果我们的系统要给第三方做授权，就实现OAuth2.0
     * 如果我们要做前后端分离，就实现token就可以了，jwt仅仅是token的一种实现方式
     * 三者的关系，我应该说明白了，希望对大家有帮助
 * ————————————————
 */


/**
 * 前后端分离：jwt Security 授权认证配置类
 */
//@Configuration
//@EnableWebSecurity
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {

    //没携带token或session异常
    @Autowired
    CustomizeAuthenticationEntryPoint authenticationEntryPoint;

    //没权限异常
    @Autowired
    CustomAccessDeineHandler customAccessDeineHandler;

    //获取用户账号密码及权限信息、权限
    @Autowired
    UserDetailsServiceImpl userDetailsServiceImpl;

    /**
     * 设置Security默认的加密方式（强hash方式加密）
     * 常见的加密方式：
         * BCryptPasswordEncoder：Spring Security 推荐使用的，使用BCrypt强哈希方法来加密。
         * MessageDigestPasswordEncoder：用作传统的加密方式加密(支持 MD5、SHA-1、SHA-256...)
         * DelegatingPasswordEncoder：最常用的，根据加密类型id进行不同方式的加密，兼容性强
         * NoOpPasswordEncoder：明文， 不做加密
     * 其他
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 将能够获取：账号、密码、角色list的实现类对象交给AuthenticationManagerBuilder
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsServiceImpl);
    }

    /**
     *
     * API 参考：
     *      authenticated() 允许认证的用户进行访问
     *      denyAll() 无条件拒绝所有访问
     *      fullyAuthenticated() 如果用户是完整认证的话（不是通过Remember-me功能认证的），就允许访问
     *      hasRole(String) 如果用户具备给定角色(用户组)的话,就允许访问
     *      hasAuthority(String) 如果用户具备给定权限的话就允许访问
     *      permitAll() 无条件允许访问
     *      rememberMe() 如果用户是通过Remember-me功能认证的，就允许访问
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        /**
         * 设置系统所有角色与权限
         *  1. security 根据request uri 匹配 permission后，得到role。
         *  2. 再根据userDetailsServiceImpl 中 new User（） 时传递的role list 做匹配。
         *  3. 如果角色在role list中，则权限认证成功，否则走没有权限处理。
         */
        initMatchersURLs(http);

        // 跨域共享
        http.cors()
                .and()
                // 跨域伪造请求限制无效
                .csrf().disable()
                .authorizeRequests()
                //允许静态资源无授权访问
//                .antMatchers(HttpMethod.GET, "/",
//                        "/*.html",
//                        "/favicon.ico",
//                        "/**/*.html",
//                        "/**/*.css",
//                        "/**/*.js",
//                        "/swagger-resources/**",
//                        "/v2/api-docs/**").permitAll()
                // 访问/data需要ADMIN角色 initMatchersURLs(http); 已绑定所有 url 和 角色之间的关系
                //此处额外在加一个
                .antMatchers("/data").hasRole("ADMIN")
                // 其余资源：任何人都可访问； html、css、js、img、initMatchersURLs(http); 没有设置的后端url 都放行
                .anyRequest().permitAll()
                .and()
                // 添加JWT登录拦截器
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                // 添加JWT鉴权拦截器
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                .sessionManagement()
                // 设置Session的创建策略为：Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 用户没有携带token、访问被security 保护的 资源时，错误处理
                // .antMatchers("/data").hasRole("ADMIN") 该代码就是设置 被 security保护的资源
                .and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                //携带token，但是用户没权限-处理
                .and().exceptionHandling().accessDeniedHandler(customAccessDeineHandler);
    }

    /**
     * 跨域配置
     * @return 基于URL的跨域配置信息
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 注册跨域配置
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }

    /**
     * @Description: 配置放行的资源
     **/
    @Override
    public void configure(WebSecurity web) throws Exception {
        //此处可配置自己需要放行的资源
        web.ignoring().antMatchers("/html/**", "/static/**","/favicon.ico")
                // 此处给 swagger 放行；不需要权限能访问的资源
                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/images/**", "/webjars/**", "/v2/api-docs", "/configuration/ui", "/configuration/security");
    }

    @Autowired
    SysUserService sysUserService;

    /**
     * 设置菜单对应角色校验规则
     * @param http HttpSecurity
     * @throws Exception
     */
    private void initMatchersURLs(HttpSecurity http) throws Exception {
        //将所有权限和角色映射关系，设置到HttpSecurity中
        Map<String, List<String>> roles = sysUserService.selectPermissions();
        for (String role : roles.keySet()) {
            System.out.println("role = " + role);
            List<String> permissions = roles.get(role);
            for (String permission : permissions) {
                //方式一：配置权限、role的绑定，适合 mysql表设计为：权限、角色、用户
                http.authorizeRequests().antMatchers(permission).hasAnyRole(role);
                //方式二：路径、权限的绑定，适合 mysql表设计为：权限、用户
                //antMatchers("/getUser").hasAuthority("query_user");
            }
        }
    }



}